package com.lly.opentelemetry

import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.os.Looper
import android.text.TextUtils
import android.util.Log
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.trace.Span
import io.opentelemetry.api.trace.Tracer
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator
import io.opentelemetry.context.Scope
import io.opentelemetry.context.propagation.ContextPropagators
import io.opentelemetry.exporter.logging.SystemOutLogRecordExporter
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter
import io.opentelemetry.sdk.OpenTelemetrySdk
import io.opentelemetry.sdk.logs.SdkLoggerProvider
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor
import io.opentelemetry.sdk.metrics.SdkMeterProvider
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader
import io.opentelemetry.sdk.resources.Resource
import io.opentelemetry.sdk.trace.SdkTracerProvider
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes
import java.util.concurrent.Callable
import java.util.concurrent.TimeUnit

object LaunchTrace {
    var serverName = "LaunchTrace"
    var host = ""
    var traceEndPoint = "4317"
    var logEndPoint = "4317"
    val tagLaunch = "Launch"
    var enable = false
    val otl by lazy {
        initOpenTelemetry()
    }

    private fun initOpenTelemetry(): OpenTelemetry {
        val resource = Resource.getDefault()
            .merge(
                Resource.create(
                    Attributes.of(
                        ResourceAttributes.SERVICE_NAME,
                        serverName
                    )
                )
            )

        val sdkTracerProvider: SdkTracerProvider = SdkTracerProvider.builder()
            .addSpanProcessor(
                BatchSpanProcessor.builder(
                    OtlpGrpcSpanExporter.builder().setEndpoint("$host:$traceEndPoint")
                        .setTimeout(30, TimeUnit.SECONDS)
                        .build()
                ).build()
            )
            .setResource(resource)
            .build()
        val sdkBuilder = OpenTelemetrySdk.builder()
        if (enable) {
            sdkBuilder.setTracerProvider(sdkTracerProvider)
        }
        if (enable) {
            val sdkMeterProvider: SdkMeterProvider = SdkMeterProvider.builder()
                .registerMetricReader(
                    PeriodicMetricReader.builder(
                        OtlpGrpcMetricExporter.builder()
                            .setEndpoint("$host:$traceEndPoint")
                            .setTimeout(30, TimeUnit.SECONDS)
                            .build()
                    ).build()
                )
                .setResource(resource)
                .build()
            val sdkLoggerProvider: SdkLoggerProvider = SdkLoggerProvider.builder()
                .addLogRecordProcessor(
                    BatchLogRecordProcessor.builder(
                        OtlpGrpcLogRecordExporter.builder()
                            .setEndpoint("$host:$logEndPoint")
                            .setTimeout(30, TimeUnit.SECONDS)
                            .build()
                    ).build()
                )
                .addLogRecordProcessor(
                    BatchLogRecordProcessor.builder(SystemOutLogRecordExporter.create()).build()
                )
                .setResource(resource)
                .build()
            sdkBuilder.setMeterProvider(sdkMeterProvider)
            sdkBuilder.setLoggerProvider(sdkLoggerProvider)
        }

        return sdkBuilder
            .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
            .buildAndRegisterGlobal()

    }

}


abstract class TraceApplicationHelper(
    val application: Application,
    private val enable: Boolean = true,
    host: String,
) {
    init {
        LaunchTrace.enable = enable
        LaunchTrace.host = host
    }

    lateinit var tracer: Tracer
    lateinit var launchSpan: Span
    lateinit var launchScope: Scope
    var isTraceFinished = false
    var isTargetResumed = false
    val otle = LaunchTrace.otl
    fun Activity.isTarget() = this::class.java.name == getTargetActivity()
    fun Activity.name() = this::class.java.name
    val spans = HashMap<String, Span>()
    fun attachBaseContext(action: Runnable) {
        if (!enable) {
            action.run()
            return
        }
        val tag = "attachBaseContext"
        init()
        wrapSpan(tag) {
            action.run()
        }
    }

    private fun init() {
        application.registerActivityLifecycleCallbacks(object :
            Application.ActivityLifecycleCallbacks {
            override fun onActivityPreCreated(activity: Activity, savedInstanceState: Bundle?) {
                val tag = activity.name()
                if (activity is FragmentActivity) {
                    activity.supportFragmentManager.registerFragmentLifecycleCallbacks(object :
                        FragmentManager.FragmentLifecycleCallbacks() {
                        override fun onFragmentAttached(
                            fm: FragmentManager,
                            f: Fragment,
                            context: Context
                        ) {
                            spans[activity.name()]?.addEvent("onFragmentAttached:$f")
                        }

                        override fun onFragmentCreated(
                            fm: FragmentManager,
                            f: Fragment,
                            savedInstanceState: Bundle?
                        ) {
                            spans[activity.name()]?.addEvent("onFragmentCreated:$f")

                        }

                        override fun onFragmentStarted(fm: FragmentManager, f: Fragment) {
                            spans[activity.name()]?.addEvent("onFragmentStarted:$f")

                        }

                        override fun onFragmentViewCreated(
                            fm: FragmentManager,
                            f: Fragment,
                            v: View,
                            savedInstanceState: Bundle?
                        ) {
                            spans[activity.name()]?.addEvent("onFragmentViewCreated:$f")

                        }

                        override fun onFragmentResumed(fm: FragmentManager, f: Fragment) {
                            spans[activity.name()]?.addEvent("onFragmentResumed:$f")
                        }
                    }, true)

                }
                val span = tracer.spanBuilder(tag).startSpan()
                spans[tag] = span
                span.addEvent("onActivityPreCreated")
            }

            override fun onActivityPostCreated(activity: Activity, savedInstanceState: Bundle?) {
                spans[activity.name()]?.addEvent("onActivityPostCreated")
            }

            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                if (!isTraceFinished) {
                    launchSpan.addEvent("onActivityCreated:$activity")
                }
                spans[activity.name()]?.addEvent("onActivityCreated")
            }

            override fun onActivityPreStarted(activity: Activity) {
                spans[activity.name()]?.addEvent("onActivityPreStarted")
            }

            override fun onActivityPostStarted(activity: Activity) {
                spans[activity.name()]?.addEvent("onActivityPostStarted")

            }

            override fun onActivityStarted(activity: Activity) {
                if (!isTraceFinished) {
                    launchSpan.addEvent("onActivityPostStarted:$activity")
                }
                spans[activity.name()]?.addEvent("onActivityStarted")
            }

            override fun onActivityPreResumed(activity: Activity) {
                spans[activity.name()]?.addEvent("onActivityPreResumed")

            }

            override fun onActivityPostResumed(activity: Activity) {
                spans[activity.name()]?.addEvent("onActivityPostResumed")
                spans.remove(activity.name())?.apply {
                    end()
                }
                if (!isTargetResumed) {
                    launchSpan.addEvent("onActivityResumed:$activity")
                    if (activity.isTarget()) {
                        isTargetResumed = true
                        application.unregisterActivityLifecycleCallbacks(this)
                        closeLaunchTrace()
                    }
                } else {
                    spans.remove(activity.name())
                }

            }

            override fun onActivityResumed(activity: Activity) {
                spans[activity.name()]?.addEvent("onActivityResumed")
            }

            override fun onActivityPaused(activity: Activity) {
                if (!isTraceFinished) {
                    launchSpan.addEvent("onActivityPaused:$activity")
                }
            }

            override fun onActivityStopped(activity: Activity) {
                spans.remove(activity.name())?.apply {
                    addEvent("onActivityStopped")
                    end()
                }
            }

            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
            }

            override fun onActivityDestroyed(activity: Activity) {
                spans.remove(activity.name())?.apply {
                    addEvent("onActivityDestroyed")
                    end()
                }
            }
        })
        tracer = LaunchTrace.otl.getTracer(LaunchTrace.tagLaunch)
        launchSpan = tracer.spanBuilder(LaunchTrace.tagLaunch).startSpan()
        launchScope = launchSpan.makeCurrent()
    }

    fun onCreate(action: Runnable) {
        if (!enable) {
            action.run()
            return
        }
        val tag = "onCreate"
        wrapSpan(tag) {
            action.run()
        }
        Looper.getMainLooper().queue.addIdleHandler {
            Log.e(LaunchTrace.serverName, "Displayed IdleHandler")
            if (!isTraceFinished && isTargetResumed) {
                launchSpan.addEvent("IdleHandler")
                closeLaunchTrace()
            }
            !isTraceFinished
        }

    }

    private fun closeLaunchTrace() {
        isTraceFinished = true
        launchScope.close()
        launchSpan.end()
    }

    inline fun <T> wrapSpan(tag: String, action: Callable<T>): T {
        launchSpan.addEvent(tag)
        val span = tracer.spanBuilder(tag).startSpan()
        val call = action.call()
        span.end()
        return call
    }

    fun onLowMemory() {
        if (!enable) {
            return
        }

        val tag = "onLowMemory"
        launchSpan.addEvent(tag)


    }

    fun onTrimMemory(level: Int) {
        if (!enable) {
            return
        }

        val tag = "onTrimMemory"
        launchSpan.addEvent(tag)
    }

    fun onConfigurationChanged(newConfig: Configuration) {
        if (!enable) {
            return
        }
        val tag = "onConfigurationChanged"
        launchSpan.addEvent(tag)
    }

    abstract fun getTargetActivity(): String
}